bitkeeper revision 1.1041.6.1 (40e5221awCqaZfcsO_ny2z3p_6BiEg)
authorkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>
Fri, 2 Jul 2004 08:51:38 +0000 (08:51 +0000)
committerkaf24@scramble.cl.cam.ac.uk <kaf24@scramble.cl.cam.ac.uk>
Fri, 2 Jul 2004 08:51:38 +0000 (08:51 +0000)
nsplitd.c, Makefile:
  mvdir
Makefile:
  Rename: BitKeeper/deleted/.del-Makefile~9f548ce6ff509eac -> tools/nsplitd/Makefile
nsplitd.c:
  Rename: BitKeeper/deleted/.del-nsplitd.c~8b67653f8e377176 -> tools/nsplitd/nsplitd.c

.rootkeys
tools/misc/nsplitd/Makefile [new file with mode: 0644]
tools/misc/nsplitd/nsplitd.c [new file with mode: 0644]

index e843df9b042a9b0bfeb4ed1f4aa95dd2aeea51f7..c575ef31d3f8a60213a49083b06d49677d8e2006 100644 (file)
--- a/.rootkeys
+++ b/.rootkeys
 3f6dc140C8tAeBfroAF24VrmCS4v_w tools/misc/miniterm/README
 3f6dc142IHaf6XIcAYGmhV9nNSIHFQ tools/misc/miniterm/miniterm.c
 40c9c469kT0H9COWzA4XzPBjWK0WsA tools/misc/netfix
+4022a73cEKvrYe_DVZW2JlAxobg9wg tools/misc/nsplitd/Makefile
+4022a73cKms4Oq030x2JBzUB426lAQ tools/misc/nsplitd/nsplitd.c
 3f870808_8aFBAcZbWiWGdgrGQyIEw tools/misc/p4perf.h
 3f5ef5a2ir1kVAthS14Dc5QIRCEFWg tools/misc/xen-clone
 3f5ef5a2dTZP0nnsFoeq2jRf3mWDDg tools/misc/xen-clone.README
diff --git a/tools/misc/nsplitd/Makefile b/tools/misc/nsplitd/Makefile
new file mode 100644 (file)
index 0000000..c5c4c9e
--- /dev/null
@@ -0,0 +1,22 @@
+
+CC     = gcc
+CFLAGS = -Wall -O3
+CFILES = $(wildcard *.c)
+
+HDRS     = $(wildcard *.h)
+OBJS     = $(patsubst %.c,%.o,$(wildcard *.c))
+
+TARGET   = nsplitd
+
+all: $(TARGET)
+
+install: all
+
+clean:
+       $(RM) *.o $(TARGET) *~
+
+$(TARGET): $(OBJS)
+       $(CC) $(CFLAGS) -o $@ $^
+
+%.o: %.c $(HDRS) Makefile
+       $(CC) $(CFLAGS) -c -o $@ $<
diff --git a/tools/misc/nsplitd/nsplitd.c b/tools/misc/nsplitd/nsplitd.c
new file mode 100644 (file)
index 0000000..48fbd65
--- /dev/null
@@ -0,0 +1,686 @@
+/*
+ *     nsplitd.c
+ *     ---------
+ *
+ * $Id: nsplitd.c,v 2.6 1998/09/17 14:28:37 sde1000 Exp $
+ *
+ * Copyright (c) 1995, University of Cambridge Computer Laboratory,
+ * Copyright (c) 1995, Richard Black, All Rights Reserved.
+ *
+ *
+ * A complete re-implementation of DME's nsplitd for use from inetd
+ *
+ */
+
+/* The basic stream comes in (via inetd) and we then conenct to
+ * somewhere else providing a loop-through service, except we offer
+ * two other ports for connection - one of which gets a second channel
+ * using the top bit to distinguish, and the other is a master control
+ * port (normally used for gdb) which gets complete exclusive access
+ * for its duration.
+ *
+ * Originally designed for multiplexing a xwcons/telnet with a gdb
+ * post-mortem debugging session.
+ *
+ * Here is a picture:
+ *
+ *                                         port0 (from inetd)
+ *      8-bit connection                  /
+ *        made by us      <----> nsplitd <-----gdbport (default port0+2)
+ *     to host:port/tcp                  |\
+ *                                       | port1 (default port0+1)
+ *                                         \
+ *                                          control (default port0+3)
+ *
+ * If port1 is explicitly disabled (through a command-line option) then
+ * port0 becomes 8-bit clean.
+ */
+
+/*
+ * N.B.: We do NOT support 8 bit stdin/stdout usage on a
+ * /dev/... because to do that right involves much messing with ioctl
+ * and TIOC... etc.  If you want to do that sort of thing then the
+ * right way to do it is to chain this onto wconsd (which does know
+ * about and understand all the ioctl and TIOC grief).
+ */
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <sys/time.h>
+#include <sys/signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <syslog.h>
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef LOG_DAEMON
+#define LOG_DAEMON 0
+#endif
+
+#define DB(x)  /* ((x), fflush(stderr)) */
+
+extern char *optarg;
+
+extern int optind, opterr, optopt;
+
+static char *prog_name;
+
+static void usage(void)
+{
+    fprintf(stderr, "This program (%s) should be run via inetd (tcp)\n\n",
+           prog_name);
+    fprintf(stderr, "usage: %s [-h<highport>][-g<gdbport>]"
+           "[-c<ctlport>][-8] host:service\n",
+           prog_name);
+    exit(1);
+}
+
+static void fault(char *format, ...)
+{
+    va_list            ap;
+    char               logbuf[1024];
+
+    va_start(ap, format);
+    fprintf(stderr, "%s: ", prog_name);
+    vfprintf(stderr, format, ap);
+    fflush(stderr);
+    va_end(ap);
+    
+    /* XXX This is a bit dubious, but there is no vsyslog */
+    va_start(ap, format);
+    vsprintf(logbuf, format, ap);
+    syslog(LOG_ERR, logbuf);
+    va_end(ap);
+    exit(1);
+}
+
+static int getservice(char *name, unsigned short *port)
+{
+    struct servent             *se;
+
+    if (!name) return -1;
+
+    if (isdigit(name[0]))
+       *port = atoi(name);
+    else
+    {
+       if (!(se = getservbyname(name, "tcp")))
+           return -1;
+       *port = ntohs(se->s_port);
+    }
+    return 0;
+}
+
+/* 
+ *  connect_host: connect to ("name", "port")
+ */
+static int connect_host (char *name, unsigned int port)
+{
+    int                        fd;
+    struct hostent     *hostent;
+    struct sockaddr_in sin;
+    int                        on;
+    
+    if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
+       fault("socket");
+    
+    if (!(hostent = gethostbyname(name)))
+       fault("gethostbyname: %s: %s\n", name, strerror(errno));
+    
+    memset(&sin, 0, sizeof(sin));
+    sin.sin_family = AF_INET;
+    sin.sin_port   = htons (port);
+    memcpy(&sin.sin_addr.s_addr, hostent->h_addr, sizeof(struct in_addr));
+    
+    if (connect(fd, (struct sockaddr *) &sin, sizeof (sin)) < 0)
+       fault("connect: %s:%u: %s\n", name, port, strerror(errno));
+    
+    on = 1;
+    if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)) < 0)
+       syslog(LOG_WARNING, "setsockopt (TCP_NODELAY): %m");
+
+    on = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0)
+       syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
+
+    return fd;
+}
+
+/*
+ * open a tcp socket and start listening for connections on it
+ */
+static int startlistening(unsigned short port)
+{
+    int                        fd, on;
+    struct sockaddr_in sin;
+
+    if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
+       fault("socket");
+    
+    on = 1;
+    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0)
+      syslog(LOG_WARNING, "setsockopt (SO_REUSEADDR): %m");
+
+    memset(&sin, 0, sizeof(sin));
+    sin.sin_family      = AF_INET;
+    sin.sin_port        = htons (port);
+    sin.sin_addr.s_addr = INADDR_ANY;
+    if (bind(fd, &sin, sizeof(sin)) < 0)
+       fault("bind: %u: %s\n", port, strerror(errno));
+    
+    if (listen(fd, 1) < 0)
+       fault("listen: %s\n", strerror(errno));
+    
+    return fd;
+}
+
+static void noblock(int fd)
+{
+    int on=1;
+    
+    if (ioctl(fd, FIONBIO, &on) < 0)
+       fault("ioctl: FIONBIO: %s\n", strerror(errno));
+}
+
+
+/* You might not believe this, but fd_sets don't have to be a 32-bit
+ * integer.  In particular, in glibc2 it is an array of unsigned
+ * longs.  Hence, this hacked up FD_SET_rjb() that works out if it
+ * would have been a nop. */
+#define FD_SET_rjb(fd, setp) \
+do {                                           \
+    if ((fd) != 32)                            \
+       FD_SET((fd), (setp));                   \
+} while(0)
+
+#define FD_ISSET_rjb(fd, setp) (((fd) != 32)? FD_ISSET((fd), (setp)) : 0)
+
+#define MAXSIZE        256
+
+/* -----------------------------------------------------------------
+ * The main bit of the algorithm. Note we use 32 to mean not connected
+ * because this gives us 1<<32 == 0. We could have done this one
+ * character at a time, but that would have been very inefficient and
+ * not the unix way.  */
+static int debug;
+
+static void doit(int actl, int acto, int lish, int lisg, int lisc)
+{
+    int                acth, actg, actc;
+    int                gdbmode = FALSE;
+    char       gibuf[MAXSIZE], oibuf[MAXSIZE];
+    char       libuf[MAXSIZE], lobuf[MAXSIZE];
+    char       hibuf[MAXSIZE], hobuf[MAXSIZE];
+    char       ctlbuf[MAXSIZE];
+    fd_set     rdfs, wrfs, exfs;
+    int                gicc, oicc, licc, locc, hicc, hocc, ctlcc;
+    char       *giptr, *oiptr, *liptr, *loptr, *hiptr, *hoptr;
+    int                rc, fromlen;
+    struct sockaddr_in         from;
+    
+    gicc = oicc = licc = locc = hicc = hocc = ctlcc = 0;
+    acth = actg = actc = 32;                   /* XXX yummy */
+
+    noblock(actl);
+    noblock(acto);
+
+    for(;;)
+    {
+       FD_ZERO(&rdfs);
+       FD_ZERO(&wrfs);
+       FD_ZERO(&exfs);
+
+       /* always take input from the control port (if it's connected) */
+       FD_SET_rjb(actc, &rdfs);
+
+       if (gdbmode)
+       {
+           if (oicc)
+               FD_SET_rjb(actg, &wrfs);
+           else
+               FD_SET_rjb(acto, &rdfs);
+           
+           if (gicc)
+               FD_SET_rjb(acto, &wrfs);
+           else
+               FD_SET_rjb(actg, &rdfs);
+       }
+       else
+       {
+           /* There is no such thing as oibuf because its been split into
+            * lobuf and hobuf
+            */
+           if (locc || hocc)
+           {
+               if (locc)
+                   FD_SET_rjb(actl, &wrfs);
+               if (hocc)
+                   FD_SET_rjb(acth, &wrfs);
+           }
+           else
+               FD_SET_rjb(acto, &rdfs);
+           
+           if (licc)
+               FD_SET_rjb(acto, &wrfs);
+           else
+               FD_SET_rjb(actl, &rdfs);
+           
+           if (hicc)
+               FD_SET_rjb(acto, &wrfs);
+           else
+               FD_SET_rjb(acth, &rdfs);
+       }
+       
+       if (acth == 32 && lish>=0)      FD_SET_rjb(lish, &rdfs);
+       if (actg == 32)                 FD_SET_rjb(lisg, &rdfs);
+       if (actc == 32)                 FD_SET_rjb(lisc, &rdfs);
+
+       /* now make exfs the union of the read and write fd sets, plus
+        * "actl" */
+       {
+           int i;
+           exfs = rdfs;
+           for(i=0; i<32; i++)  /* XXX we only copy fd numbers up to 31 */
+               if (FD_ISSET(i, &wrfs))
+                   FD_SET_rjb(i, &exfs);
+           FD_SET_rjb(actl, &exfs);
+       }
+
+       /* XXX AND: can't print something of type fd_set as %x - it
+         * might be an array */
+       DB(fprintf(stderr, "%s: before select: %08x %08x %08x\n",
+                  prog_name, rdfs, wrfs, exfs));
+       
+       if (select(32, &rdfs, &wrfs, &exfs, NULL) < 0)
+           fault("select: %s\n", strerror(errno));
+       
+       DB(fprintf(stderr, "%s: after  select: %08x %08x %08x\n",
+                  prog_name, rdfs, wrfs, exfs));
+       
+       /* XXX it appears that a non-blocking socket may not show up
+        * correctly in exfs but instead goes readable with no data in
+        * it. Thus we check for zero and goto the appropriate close
+        * method.  */
+
+       /* Deal with exceptions */
+       if (FD_ISSET_rjb(actg, &exfs))
+       {
+       exfs_actg:
+           close(actg);
+           gdbmode = FALSE;
+           oicc = 0;
+           oiptr = oibuf;
+           actg = 32;
+           continue;           /* because assumptions changed */
+       }
+       if (FD_ISSET_rjb(acth, &exfs))
+       {
+       exfs_acth:
+           close(acth);
+           hicc = hocc = 0;
+           hiptr = hibuf;
+           hoptr = hibuf;
+           acth = 32;
+           continue;           /* because assumptions changed */
+       }
+       if (FD_ISSET_rjb(actl, &exfs) ||
+           FD_ISSET_rjb(acto, &exfs))
+       {
+       exfs_actl:
+       exfs_acto:
+           /* Thats all folks ... */
+           break;
+       }
+       if (FD_ISSET_rjb(actc, &exfs))
+       {
+       exfs_ctl:
+           close(actc);
+           actc = 32;
+           ctlcc = 0;
+           continue;
+       }
+
+       /* Deal with reading */
+       if (FD_ISSET_rjb(acto, &rdfs))
+       {
+           if ((oicc = read(acto, oiptr = oibuf, MAXSIZE)) < 0)
+               fault("read acto: %d: %s\n", oicc, strerror(errno));
+           if (!oicc) goto exfs_acto;
+           
+           if (!gdbmode)
+           {
+               int t;
+
+               assert((locc == 0) && (hocc == 0));
+               loptr = lobuf;
+               hoptr = hobuf;
+               
+               if (lish>=0) {
+                   for(t=0; t<oicc; t++)
+                       if (oibuf[t] & 0x80)
+                           hobuf[hocc++] = oibuf[t] & 0x7f;
+                       else
+                           lobuf[locc++] = oibuf[t];
+               } else {
+                   for (t=0; t<oicc; t++)
+                       lobuf[locc++] = oibuf[t];
+               }
+               /* If no high connection scratch that */
+               if (acth == 32)
+                   hocc=0;
+           }
+       }
+       if (FD_ISSET_rjb(actl, &rdfs))
+       {
+           if ((licc = read(actl, liptr = libuf, MAXSIZE)) < 0)
+               fault("read actl: %d: %s\n", licc, strerror(errno));
+           if (!licc) goto exfs_actl;
+       }
+       if (FD_ISSET_rjb(acth, &rdfs))
+       {
+           int t;
+           
+           if ((hicc = read(acth, hiptr = hibuf, MAXSIZE)) < 0)
+               fault("read acth: %d: %s\n", hicc, strerror(errno));
+           if (!hicc) goto exfs_acth;
+           for(t=0; t<hicc; t++)
+               hibuf[t] |= 0x80;
+       }
+       if (FD_ISSET_rjb(actg, &rdfs))
+       {
+           if ((gicc = read(actg, giptr = gibuf, MAXSIZE)) < 0)
+               fault("read actg: %d: %s\n", gicc, strerror(errno));
+           if (debug) write(1, giptr, gicc);           /* XXX */
+           if (!gicc) goto exfs_actg;
+       }
+       if (FD_ISSET_rjb(actc, &rdfs))
+       {
+           if ((ctlcc = read(actc, ctlbuf, MAXSIZE)) < 0)
+               fault("read actc: %d: %s\n", ctlcc, strerror(errno));
+           if (debug) write(1, ctlbuf, gicc);
+           if (!ctlcc) goto exfs_ctl;
+           if (ctlbuf[0] == 'r') /* reset command */
+           {
+               syslog(LOG_INFO, "reset command read, exiting");
+               if (debug) write(1, "reseting\n", sizeof("reseting\n"));
+               break;
+           }
+       }
+       
+       /* Deal with writing */
+       if (FD_ISSET_rjb(actg, &wrfs))
+       {
+           /* We must be in gdb mode so send oi buffer data */
+           assert(gdbmode);
+           if (debug) write(2, oiptr, oicc);           /* XXX */
+           if ((rc = write(actg, oiptr, oicc)) <= 0)
+               fault("write actg: %d: %s\n", rc, strerror(errno));
+           oiptr += rc;
+           oicc  -= rc;
+       }
+       if (FD_ISSET_rjb(actl, &wrfs))
+       {
+           if ((rc = write(actl, loptr, locc)) <= 0)
+               fault("write actl: %d: %s\n", rc, strerror(errno));
+           loptr += rc;
+           locc  -= rc;
+       }
+       if (FD_ISSET_rjb(acth, &wrfs))
+       {
+           if ((rc = write(acth, hoptr, hocc)) <= 0)
+               fault("write acth: %d: %s\n", rc, strerror(errno));
+           hoptr += rc;
+           hocc  -= rc;
+       }
+       if (FD_ISSET_rjb(acto, &wrfs))
+       {
+           /* If in gdb mode send gdb input, otherwise send low data
+              preferentially */
+           if (gdbmode)
+           {
+               assert(gicc);
+               if ((rc = write(acto, giptr, gicc)) <= 0)
+                   fault("write acto: %d: %s\n", rc, strerror(errno));
+               giptr += rc;
+               gicc  -= rc;
+           }
+           else
+           {
+               if (licc)
+               {
+                   if ((rc = write(acto, liptr, licc)) <= 0)
+                       fault("write acto: %d: %s\n", rc, strerror(errno));
+                   liptr += rc;
+                   licc  -= rc;
+               }
+               else
+               {
+                   assert(hicc);
+                   if ((rc = write(acto, hiptr, hicc)) <= 0)
+                       fault("write acto: %d: %s\n", rc, strerror(errno));
+                   hiptr += rc;
+                   hicc  -= rc;
+               }
+           }
+       }
+       
+       /* Deals with new connections */
+       if ((acth == 32) && lish>=0 && (FD_ISSET_rjb(lish, &rdfs)))
+       {
+           fromlen = sizeof(from);
+           if ((acth = accept(lish, &from, &fromlen)) < 0)
+           {
+               syslog(LOG_WARNING, "accept: %m");
+               acth = 32;
+           }
+           else
+           {
+               noblock(acth);
+               hicc = hocc = 0;
+               syslog(LOG_INFO, "highbit client peer is %s:%u\n",
+                      inet_ntoa(from.sin_addr), ntohs(from.sin_port));
+           }
+       }
+       
+       if ((actg == 32) && (FD_ISSET_rjb(lisg, &rdfs)))
+       {
+           fromlen = sizeof(from);
+           if ((actg = accept(lisg, &from, &fromlen)) < 0)
+           {
+               syslog(LOG_WARNING, "accept: %m");
+               actg = 32;
+           }
+           else
+           {
+               noblock(actg);
+               gicc = 0;
+               gdbmode = TRUE;
+               syslog(LOG_INFO, "gdb client peer is %s:%u\n",
+                      inet_ntoa(from.sin_addr), ntohs(from.sin_port));
+           }
+       }
+
+       if ((actc == 32) && (FD_ISSET_rjb(lisc, &rdfs)))
+       {
+           fromlen = sizeof(from);
+           if ((actc = accept(lisc, &from, &fromlen)) < 0)
+           {
+               syslog(LOG_WARNING, "accept (ctl): %m");
+               actc = 32;
+           }
+           else
+           {
+               noblock(actc);
+               syslog(LOG_INFO, "ctl client peer is %s:%u\n",
+                      inet_ntoa(from.sin_addr), ntohs(from.sin_port));
+           }
+       }
+           
+       /* Back to top of loop */
+    }
+    
+    /* We are bailing because one of the primary connections has gone
+     * away. We close these all explicitly here because that way the
+     * timeout on reusing the port numbers is smnaller. */
+    
+    close(acth);
+    close(actg);
+    /* XXX AND: why are we closing all these "character counts" ?? */
+    close(gicc);
+    close(oicc);
+    close(licc);
+    close(locc);
+    close(hicc);
+    close(hocc);
+}
+
+/*
+ * ------------------------------------------------------------
+ */
+int main(int argc, char **argv)
+{
+    /* In general, suffix "l" is low channel, "h" is high channel, "g"
+     * is gdb channel, "c" is control channel and "o" is output channel.
+     */
+    struct sockaddr_in         from;
+    int                                infd = 0, outfd;
+    unsigned short             portl, porth, portg, portc, porto;
+    int                                on = 1, c;
+    char                       *outname, *outservice;
+    int                                fromlen;
+    int                                lish, lisg, lisc;
+#if 0
+    FILE                       *newerr;
+#endif /* 0 */
+    
+    prog_name = argv[0];
+
+    if (isatty(infd))
+       usage();
+
+    /* Here, then not just a simple idiot. */
+
+    signal(SIGPIPE, SIG_IGN);
+
+    openlog(prog_name, LOG_PID, LOG_DAEMON);
+
+    fromlen = sizeof(from);
+    if (getsockname(infd, &from, &fromlen) < 0)
+       fault("getsockname: %s", strerror(errno));
+    if ((fromlen != sizeof(from)) || (from.sin_family != AF_INET))
+       fault("not an inet socket (family=%d)\n", from.sin_family);
+    
+    portl = ntohs(from.sin_port);
+    porth = portl+1;
+    portg = porth+1;
+    portc = portg+1;
+
+    fromlen = sizeof(from);
+    if (getpeername(infd, &from, &fromlen) < 0)
+       fault("getpeername: %s", strerror(errno));
+    if ((fromlen != sizeof(from)) || (from.sin_family != AF_INET))
+       fault("not an inet socket (family=%d)\n", from.sin_family);
+
+    syslog(LOG_INFO, "on port %u peer is %s:%u\n", portl,
+          inet_ntoa(from.sin_addr), ntohs(from.sin_port));
+    
+    if (setsockopt(infd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) < 0)
+       syslog(LOG_WARNING, "setsockopt (SO_KEEPALIVE): %m");
+
+    /* from here on, we map stderr to output on the connection so we can
+     * report errors to the remote user.
+     */
+#if 0
+    if (!(newerr = fdopen(infd, "w")))
+       syslog(LOG_WARNING, "fdopen: %m");
+    else
+       *stderr = *newerr;
+#endif
+       
+    while((c = getopt(argc, argv, "d8h:g:c:")) != EOF)
+    {
+       switch(c)
+       {
+       case 'd':
+           debug++;
+           break;
+           
+       case 'h':
+           /* high bit port */
+           if (getservice(optarg, &porth) < 0)
+               fault("getservice failed (high port '%s')\n", optarg);
+           break;
+           
+       case 'g':
+           /* gdb port */
+           if (getservice(optarg, &portg) < 0)
+               fault("getservice failed (gdb port '%s')\n", optarg);
+           break;
+
+       case 'c':
+           /* control port */
+           if (getservice(optarg, &portc) < 0)
+               fault("getservice failed (control port '%s')\n", optarg);
+           break;
+
+       case '8':
+           /* 8-bit clean; no high port */
+           porth=0;
+           break;
+
+       default:
+           fault("bad argument list!\n");
+       }
+    }
+    
+    if (argc != optind + 1)
+       fault("unparsed arguments (%d!=%d)\n", argc, optind+1);
+
+    outname = argv[optind];
+    if (!(outservice = strchr(outname, ':')))
+       fault("output arg '%s' doesn't contain ':'\n", outname);
+    *outservice++ = 0;
+    if (getservice(outservice, &porto) < 0)
+       fault("getservice failed (output port '%s')\n", outservice);
+    
+    /* Time to start the sockets */
+
+    if (porth) {
+       lish  = startlistening(porth);
+    } else {
+       lish  = -1;
+    }
+    lisg  = startlistening(portg);
+    lisc  = startlistening(portc);
+    
+    outfd = connect_host(outname, porto);
+    
+    doit(infd, outfd, lish, lisg, lisc);
+
+    syslog(LOG_INFO, "terminating normally\n");
+
+    fclose(stderr);
+
+    closelog();
+    exit(0); 
+}
+
+/* End $Id: nsplitd.c,v 2.6 1998/09/17 14:28:37 sde1000 Exp $ */